// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml;

namespace CIP4.Common
{
    public class Globals
    {
// ReSharper disable InconsistentNaming
// names are public - hard to change

        /// <summary>
        /// CIP4 Command Language version
        /// </summary>
        public static string cicNS { get { return string.Format(CIP4Versions.cicNS2, CIP4Versions.CIPlVersionDomain); } }

        /// <summary>
        /// CIP4 Item version
        /// </summary>
        public static string ciiNS { get { return string.Format(CIP4Versions.CIP4NS2, CIP4Versions.CIPlVersionDomain); } }

        /// <summary>
        /// CIP4 Rules version
        /// </summary>
        public static string cirNS { get {return string.Format(CIP4Versions.cirNS2, CIP4Versions.CIPlVersionDomain); } }

        /// <summary>
        /// Catchall namespace for schemas derived from user defined types
        /// </summary>
        public static string cioNS { get { return string.Format(CIP4Versions.cioNS1, CIP4Versions.CIPlVersionDomain); } }
// ReSharper restore InconsistentNaming


        /// <summary>
        /// CIP4 Command Language version
        /// </summary>
        public static string GetcicNS(string versionDomain)
        {
            return string.Format(CIP4Versions.cicNS2, versionDomain);
        }


        /// <summary>
        /// CIP4 Item version
        /// </summary>
        public static string GetciiNS(string versionDomain)
        {
            return string.Format(CIP4Versions.CIP4NS2, versionDomain);
        }

        /// <summary>
        /// CIP4 Rules version
        /// </summary>
        public static string GetcirNS(string versionDomain)
        {
            return string.Format(CIP4Versions.cirNS2, versionDomain);
        }

        /// <summary>
        /// Catchall namespace for schemas derived from user defined types
        /// </summary>
        public static string GetcioNS(string versionDomain)
        {
            return string.Format(CIP4Versions.cioNS1, versionDomain);
        }

        public static bool IsAssemblyResolverSetup = SetIsAssemblyResolverSetup();

        public static bool SetIsAssemblyResolverSetup()
        {
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolve;
            return true;
        }

        private static List<Assembly> _assemblies;
        private static List<Assembly> Assemblies
        {
            get
            {
                if (_assemblies == null || !IsAssemblyResolverSetup)
                {
                    _assemblies = new List<Assembly>();
                }
                return _assemblies;
            }
        }

        static Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
        {
            string fileName = args.Name.Split(new[] { ',' })[0];
            return AssemblyResolve(fileName);
        }

        public static void AddAssemblyPath(string path, LoggerDelegate logger)
        {
            DirectoryInfo dir = new DirectoryInfo(path);
            if (!dir.Exists)
                logger(MajorEnum.Error, MinorEnum.AssemblyPathDoesNotExist, "The directory corresponding to the given Assembly path {0} does not exist", new object[] { path });
            foreach (string value in AssemblyPaths)
                if (value == dir.FullName)
                    return;
            AssemblyPaths.Add(dir.FullName);
        }

        public static void RemoveAssemblyPath(string path)
        {
            DirectoryInfo dir = new DirectoryInfo(path);
            if (DataProviderLocations.Contains(dir.FullName))
                return;
            AssemblyPaths.RemoveAll(p => p == dir.FullName);
        }

        private static List<string> _assemblyPaths;
        private static List<string> AssemblyPaths
        {
            get { return _assemblyPaths ?? (_assemblyPaths = new List<string>(DataProviderLocations)); }
        }

        public static Assembly AssemblyGet(string fileName)
        {
            lock (Assemblies)
            {
                foreach (Assembly ass in Assemblies)
                {
                    //Name could be in the form
                    //      name, version junk
                    // or just
                    //     name
                    if (ass.FullName.Split(new[] { ',' })[0] == fileName)
                    {
                        return ass;
                    }
                }
                return null;
            }
        }

        public static Assembly AssemblyResolve(string fileName, string otherLocation)
        {
            return AssemblyResolve(fileName, AssemblyPaths.Concat(new List<string> { otherLocation }));
        }

        public static Assembly AssemblyResolve(string fileName)
        {
            return AssemblyResolve(fileName, AssemblyPaths);
        }

        public static Assembly AssemblyResolve(string fileName, IEnumerable<string> assemblyPaths)
        {
            lock (Assemblies)
            {
                foreach (Assembly ass in Assemblies)
                {
                    //Name could be in the form
                    //      name, version junk
                    // or just
                    //     name
                    if (ass.FullName.Split(new[] { ',' })[0] == fileName)
                    {
                        return ass;
                    }
                }
                foreach (string path in assemblyPaths)
                {
                    if (File.Exists(Path.Combine(path, fileName + ".dll")))
                    {
                        Assembly ass = Assembly.LoadFrom(Path.Combine(path, fileName + ".dll"));
                        Assemblies.Add(ass);
                        DefaultLogger(MajorEnum.Trace, MinorEnum.AssemblyAddedToAssemblyCache, "Current thread {0}: Assembly {1} added to the assembly cache", new object[] { System.Threading.Thread.CurrentThread.ManagedThreadId, Path.Combine(path, fileName + ".dll") });
                        return ass;
                    }
                    if (File.Exists(Path.Combine(path, fileName + ".exe")))
                    {
                        Assembly ass = Assembly.LoadFrom(Path.Combine(path, fileName + ".exe"));
                        Assemblies.Add(ass);
                        DefaultLogger(MajorEnum.Trace, MinorEnum.AssemblyAddedToAssemblyCache, "Current thread {0}: Assembly {1} added to the assembly cache", new object[] { System.Threading.Thread.CurrentThread.ManagedThreadId, Path.Combine(path, fileName + ".dll") });
                        return ass;
                    }
                }
                return null;
            }
        }

        public static string GetAssemblyPathsString()
        {
            string result = string.Empty;
            foreach (string path in AssemblyPaths)
                result += result.Seperator(";") + path;
            return result;
        }

        public static List<string> DataProviderLocations
        {
            get
            {
                return new List<string> 
                {
                    typeof(Globals).Assembly.Location.Substring(0, typeof(Globals).Assembly.Location.LastIndexOf("\\")),
                    CIP4Bin
                };
            }
        }

        private static string _binariesDirectoryName;
        public static string BinariesDirectoryName
        {
            get
            {
                if (!string.IsNullOrEmpty(_binariesDirectoryName))
                    return _binariesDirectoryName;
                return "Binaries";
            }
            set
            {
                if (_binariesDirectoryName == value)
                    return;
                if (_assemblies != null)
                    throw new ApplicationException("You cannot change the Binaries directory name once CIPl has started loading assemblies");
                _binariesDirectoryName = value;
            }
        }

        public static string CIP4Bin
        {
            get
            {
                return Path.Combine(CIP4Home, BinariesDirectoryName);
            }
        }

        private static string _cip4Home;
        public static string CIP4Home
        {
            get
            {
                if (string.IsNullOrEmpty(_cip4Home))
                {
                    _cip4Home = Environment.GetEnvironmentVariable("CIP4_HOME", EnvironmentVariableTarget.Process);
                    _cip4Home = _cip4Home ?? Environment.GetEnvironmentVariable("CIP4_HOME", EnvironmentVariableTarget.User);
                    _cip4Home = _cip4Home ?? Environment.GetEnvironmentVariable("CIP4_HOME", EnvironmentVariableTarget.Machine);
                    if (_cip4Home == null)
                    {
                        throw new ApplicationException(@"You must set the CIP4_HOME environment variable to the Directory that contains the \Binaries directory");
                    }
                }
                return _cip4Home;
            }
            set
            {
                if (string.IsNullOrEmpty(_cip4Home))
                {
                    DefaultLogger(MajorEnum.Trace, MinorEnum.Generic,
                        "Setting setting _CIP4Home to {0}",
                        new object[] { value });
                    _cip4Home = value;
                }
                else
                    DefaultLogger(
                        MajorEnum.Error, MinorEnum.CIP4HomeResetAttempt,
                        "There was an attempt to change the CIP4 Home value after it has been used - applications cn only set it once and must do so before it has been used",
                        new object[] { });
            }
        }

        public static string ConnectionsFileLocation
        {
            get
            {
                return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"CIP4\CIP4Connections.xml");
            }
        }

        public static string ProgramFilesDirectory
        {
            get
            {
                if (IntPtr.Size == 8)
                {
                    if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("PROGRAMFILES(X86)")))
                        return Environment.GetEnvironmentVariable("PROGRAMFILES(X86)");
                    throw new ApplicationException("This seems to be a 64 bit system but the PROGRAMFILES(X86) environment variable is not defined, so not sure where program files are supposet to go");
                }
                if (IntPtr.Size == 4)
                {
                    if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("PROGRAMFILES")))
                        return Environment.GetEnvironmentVariable("PROGRAMFILES");
                    return Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
                }
                throw new ApplicationException("This application only runs on 32 or 64 bit systems");
            }
        }

        public static decimal MaxMaxOccurs = 79228162514264M;

        static LoggerDelegate _defaultLogger;
        public static LoggerDelegate DefaultLogger
        {
            get
            {
                if (_defaultLogger == null)
                    return InternalDefaultLogger;
                return _defaultLogger;
            }
            set
            {
                _defaultLogger = value;
            }
        }

        public static bool LoggerWrapper(MajorEnum status, MinorEnum minorStatus, string message, object[] paras, LoggerDelegate logger)
        {
            if (logger(status, minorStatus, message, paras) == LoggerResponseEnum.keepGoing)
                return true;
            
            throw new ArgumentException(string.Format("Unable to keep going on {0} - {1}", status, string.Format(message, paras)));
        }

        private static LoggerResponseEnum InternalDefaultLogger(MajorEnum status, MinorEnum minorStatus, string message, object[] paras)
        {
            Console.WriteLine(string.Format("{0} {1}.{2} - {3}", DateTime.Now.ToString("HH:mm:ss"), status, minorStatus, string.Format(message, paras)));
            return status == MajorEnum.Error ? LoggerResponseEnum.giveUp : LoggerResponseEnum.keepGoing;
        }

        public static Guid GetHashValue(string text)
        {
            System.Security.Cryptography.MD5CryptoServiceProvider md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
            char[] charValue = text.ToCharArray();
            byte[] byteValue = new byte[text.Length * 2];

            for (Int32 index = 0; index < charValue.Length; index++)
            {
                byteValue[index * 2] = Convert.ToByte((charValue[index] & 0xFF00) >> 8);
                byteValue[index * 2 + 1] = Convert.ToByte(charValue[index] & 0xFF);
            }

            return new Guid(BitConverter.ToString(md5.ComputeHash(byteValue)).Replace("-", ""));
        }

        /// <summary>
        /// This controls how DateTimes are serialized in xml and how dates are serialized for the json item ModifiedDate date property
        /// be very careful about changing it as you could invalidate existing items.
        /// </summary>
        public static XmlDateTimeSerializationMode DateTimeSerializationMode { get { return XmlDateTimeSerializationMode.RoundtripKind; } }
    }

    public delegate LoggerResponseEnum LoggerDelegate(MajorEnum status, MinorEnum minorStatus, string message, object[] paras);

    public delegate bool LoggerWrapperDelegate(MajorEnum status, MinorEnum minorStatus, string message, object[] paras, LoggerDelegate logger);

    public delegate ISerializerType GetSerializerDelegate(SerializerKindEnum serializerKind);

    public delegate object SerializeToDataDelegate(object value, ISerializerType serializerWrapper);
}
